﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SLAU
{
    public class Matrix
    {
        private double[,] coeffs;

        /// <summary>
        /// Elements of matrix
        /// </summary>
        public double[,] Elements
        {
            get 
            { 
                return this.coeffs; 
            }
            set 
            {
                if (value.GetLength(0) != value.GetLength(1))
                {
                    throw new MatrixMinorIncorrectDimensionsException(value.GetLength(0), value.GetLength(1));
                }
                this.coeffs = value;
            }
        }

        /// <summary>
        /// Returns length of the specified dimesion
        /// </summary>
        /// <param name="dimension_index">dimesion index (0 or 1)</param>
        /// <returns>dimension length</returns>
        /// <exception cref="IndexOutOfRangeExceptio"></exception>
        public int GetDimensions(int dimension_index)
        {
            return coeffs.GetLength(dimension_index);
        }

        public Matrix()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="elements">Elements 2D array</param>
        /// <exception cref="NullReferenceException"></exception>
        public Matrix(double[,] elements)
        {
            this.Elements = elements;
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="source">Source matrix</param>
        public Matrix(Matrix source)
        {
            this.Elements = new double [source.GetDimensions(0),source.GetDimensions(1)];
            for (int i = 0; i < source.GetDimensions(0); i++)
            {
                for (int j = 0; j < source.GetDimensions(1); j++)
                {
                    this.Elements[i, j] = source.Elements[i, j];
                }
            }
        }

        /// <summary>
        /// Copy method
        /// </summary>
        /// <param name="destination">Destination matrix</param>
        public void CopyTo(Matrix destination)
        {
            destination.Elements = new double[this.GetDimensions(0), this.GetDimensions(1)];
            for (int i = 0; i < this.GetDimensions(0); i++)
            {
                for (int j = 0; j < this.GetDimensions(1); j++)
                {
                    destination.Elements[i, j] = this.Elements[i, j];
                }
            }
        }

        /// <summary>
        /// Indexer
        /// </summary>
        /// <param name="index0">Index 0</param>
        /// <param name="index1">Index 1</param>
        /// <returns></returns>
        public double this[int index0, int index1]
        {
            get
            {
                if (index0 < 0 || index1 < 0 || index0 >= this.GetDimensions(0) || index1 >= this.GetDimensions(1))
                {
                    throw new IndexOutOfRangeException();
                }
                return this.coeffs[index0, index1];
            }
            set
            {
                if (index0 < 0 || index1 < 0 || index0 >= this.GetDimensions(0) || index1 >= this.GetDimensions(1))
                {
                    throw new IndexOutOfRangeException();
                }
                this.coeffs[index0, index1] = value;
            }
        }

        /// <summary>
        /// Calculates determinant of the matrix
        /// </summary>
        /// <returns></returns>
        public double Determinant()
        {
            return _MinorDeterminant(this.coeffs);
        }

        /// <summary>
        /// Calculates determinant of the minor
        /// </summary>
        /// <param name="elements">minor</param>
        /// <returns></returns>
        private static double _MinorDeterminant(double[,] elements)
        {
            if (elements == null)
            {
                throw new NullReferenceException();
            }
            if (elements.GetLength(0) != elements.GetLength(1))
            {
                throw new MatrixMinorIncorrectDimensionsException(elements.GetLength(0), elements.GetLength(1));
            }

            int count = elements.GetLength(0);

            switch (count)
            {
                case 1:
                    return elements[0, 0];
                case 2:
                    return elements[0, 0] * elements[1, 1] - elements[1, 0] * elements[0, 1];
                default:
                    double det = 0.0;
                    int sign = 1;
                    
                    for (int c = 0; c < count; c++)
                    {
                        double[,] minor = new double[count - 1, count - 1];

                        for (int i = 1; i < count; i++)
                        {
                            for (int j = 0; j < c; j++) minor[i - 1, j] = elements[i, j];
                            for (int j = c; j < count; j++) minor[i - 1, j] = elements[i, j + 1];
                        }

                        det += elements[0, c] * sign * _MinorDeterminant(minor);
                        sign *= -1;
                    }

                    return det;
            }

        }
    }
}
